package com.icesoft.core.dao.suppose;

import com.icesoft.core.dao.base.Model;
import com.icesoft.core.dao.criteria.FromBuilder;
import com.icesoft.core.dao.criteria.QuerySupport;
import com.icesoft.core.dao.criteria.SelectBuilder;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.persistence.Cacheable;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.List;

@Slf4j
@AllArgsConstructor
@Component
public class HqlQuerySuppose {
    @Getter
    private EntityManager entityManager;

    public <D> List<D> findList(SelectBuilder selectBuilder) {
        QuerySupport querySupport = new QuerySupport(selectBuilder);
        return listResult(querySupport);
    }

    public <D> D findSingle(SelectBuilder selectBuilder) {
        QuerySupport querySupport = new QuerySupport(selectBuilder);
        return singleResult(querySupport);
    }

    public boolean isExist(FromBuilder fromBuilder) {
        return findSingle(SelectBuilder.isExist().builder(fromBuilder.offsetLimit(0, 1))) != null;
    }

    public int count(FromBuilder fromBuilder) {
        SelectBuilder selectBuilder = SelectBuilder.count().builder(fromBuilder);
        return ((Long) findSingle(selectBuilder)).intValue();
    }

    public <T extends Model> List<T> findAll(Class<T> clazz) {
        long startTime = System.currentTimeMillis();
        List<T> models = entityManager.createQuery("from " + FromBuilder.getEntityName(clazz)).getResultList();
        if (log.isDebugEnabled()) {
            log.debug("loadAll {} size : {}，用时：{}ms", clazz.getSimpleName(), models.size(),
                    System.currentTimeMillis() - startTime);
        }
        return models;
    }

    public <T extends Model> int countAll(Class<T> clazz) {
        long startTime = System.currentTimeMillis();
        Long count = (Long) entityManager.createQuery("select count(*) from " + FromBuilder.getEntityName(clazz)).getSingleResult();
        if (log.isDebugEnabled()) {
            log.debug("countAll {} : {}，用时：{}ms", clazz.getSimpleName(), count, System.currentTimeMillis() - startTime);
        }
        return count.intValue();
    }

    public <D> List<D> listResult(QuerySupport querySupport) {
        long startTime = System.currentTimeMillis();
        List<D> models = createQuery(querySupport).getResultList();
        long time = System.currentTimeMillis() - startTime;
        if (time < 1000) {
            log.debug("list查询：{}，size:{}，用时：{}ms", querySupport, models.size(), time);
        } else {
            log.warn("慢list查询：{}，size:{}，用时：{}ms", querySupport, models.size(), time);
        }
        return models;
    }


    public <D> D singleResult(QuerySupport querySupport) {
        long startTime = System.currentTimeMillis();
        try {
            D data = (D) createQuery(querySupport).getSingleResult();
            long time = System.currentTimeMillis() - startTime;
            if (time < 1000) {
                log.debug("single查询：{}，用时：{}ms", querySupport, time);
            } else {
                log.warn("慢single查询：{}，用时：{}ms", querySupport, time);
            }
            return data;
        } catch (NoResultException e) {
            if (log.isDebugEnabled()) {
                log.debug("unique查询返回 null，{}", querySupport);
            }
        }
        return null;
    }

    public Query createQuery(String hql) {
        return getEntityManager().createQuery(hql);
    }

    private Query createQuery(QuerySupport querySupport) {
        querySupport.build();
        log.trace("创建hql查询语句：{}", querySupport);
        String hql = querySupport.getHql();
        Query query = entityManager.createQuery(hql);
        bindValue(query, querySupport);

        setHibernateQuery(query, querySupport.getFromTable());
        return query;
    }

    private void setHibernateQuery(Query query, Class<?> modelClass) {
        if (isUseHibernateQueryCache(modelClass)) {
            log.trace("[{}] use query cache", modelClass.getName());
            org.hibernate.query.Query hibernateQuery = query.unwrap(org.hibernate.query.Query.class);
            hibernateQuery.setCacheable(true);
        }
    }

    private boolean isUseHibernateQueryCache(Class<?> modelClass) {
        return modelClass.isAnnotationPresent(Cacheable.class);
    }

    private void bindValue(Query query, QuerySupport querySupport) {
        if (querySupport.getPropertys() != null) {
            querySupport.getPropertys().forEach(p -> {
                if (p.getValue() instanceof QuerySupport) {
                    bindValue(query, (QuerySupport) p.getValue());
                } else {
                    query.setParameter(p.getParameterName(), p.getValue());
                }

            });
        }
        if (!querySupport.isCountQuery()) {
            if (querySupport.getLimit() > 0) {
                query.setMaxResults(querySupport.getLimit());
            }
            if (querySupport.getOffset() > 0) {
                query.setFirstResult(querySupport.getOffset());
            }
        }
    }

}
